Skip to content

feat: adding attachments to snippets. - #3421

Open
lorenzo132 wants to merge 16 commits into
developmentfrom
users/lorenzo/snippet-attachment-support
Open

feat: adding attachments to snippets.#3421
lorenzo132 wants to merge 16 commits into
developmentfrom
users/lorenzo/snippet-attachment-support

Conversation

@lorenzo132

Copy link
Copy Markdown
Member

This adds the ability to add attachments to snippets.

This adds the ability to add attachments to snippets
@StephenDaDev

Copy link
Copy Markdown
Member

I have began a review which will result in a "changes requested" verdict. I will need additional time to complete a full review. I hope to have it completed by sometime around 12PM tomorrow, Eastern.

@martinbndr martinbndr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Is it intented, that an image is sent seperate comparing to the normal reply? If not maybe we should consider letting images being added to the embed aswell to have it consistent to the replying?

@lorenzo132

lorenzo132 commented Dec 23, 2025

Copy link
Copy Markdown
Member Author
image Is it intented, that an image is sent seperate comparing to the normal reply? If not maybe we should consider letting images being added to the embed aswell to have it consistent to the replying?

I have changed the behavior, now it just sets the attachment.(I was half asleep when i made this lolololol)

Copilot AI lite review requested due to automatic review settings August 2, 2026 11:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for storing, managing, and sending file attachments associated with snippets, including persisting snippet attachments in MongoDB GridFS and relaying them through the existing thread send pipeline.

Changes:

  • Persist snippet attachments in MongoDB GridFS with upload/download/delete APIs and a configurable max attachment size.
  • Extend snippet commands (snippet add/edit/remove) to accept/manage an optional attachment and display attachment presence in snippet views.
  • Attach downloaded snippet files to outgoing thread messages (including embedding snippet images via attachment://).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
core/thread.py Adds files_to_upload pipeline and special handling for embedded snippet images when sending thread messages.
core/config.py Introduces snippet_attachment_max_size config key and conversion handling for MB-based values.
core/config_help.json Documents the new snippet_attachment_max_size config option.
core/clients.py Adds GridFS bucket + upload/download/delete methods for snippet attachments.
cogs/modmail.py Updates snippet CRUD commands to support attachments (validation, confirmation, GridFS persistence).
bot.py Downloads snippet attachments at invocation time and wraps them to flow through existing attachment sending logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core/thread.py
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread bot.py Outdated
Comment thread core/thread.py Outdated
Copilot AI review requested due to automatic review settings August 2, 2026 11:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (4)

core/thread.py:2020

  • ext includes forwarded attachments (added just above), but the loop only iterates over message.attachments, so forwarded attachments appended to ext are never classified into images/attachments and won't be included in the outgoing embed/log.
        for i, a in enumerate(message.attachments):
            attachment = ext[i]
            if getattr(a, "is_snippet_attachment", False):

bot.py:1413

  • This replaces the original message attachments with only the snippet attachment. If a user invokes a snippet while also attaching files, those files will be silently dropped.
            if attachment is not None:
                snippet_message = copy.copy(message)
                snippet_message.attachments = [attachment]
                ctx.message = snippet_message

bot.py:1386

  • This overwrites any attachments present on the original alias-invoking message when a snippet attachment exists, so user-provided attachments would be dropped instead of being sent along with the snippet attachment.

This issue also appears on line 1410 of the same file.

                        attachment = await self._download_snippet_attachment(snippet_data)
                        if attachment is not None:
                            context_message.attachments = [attachment]
                    else:

cogs/modmail.py:363

  • The help text hard-codes a 10 MB limit, but the actual limit is configurable via snippet_attachment_max_size (default 10). This can mislead users if the config is changed.
        You can also attach a file (max 10 MB) to include with the snippet.

Copilot AI review requested due to automatic review settings August 2, 2026 11:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

cogs/modmail.py:399

  • confirm_msg is only defined inside the if ctx.message.attachments: block, but it’s referenced later unconditionally (if confirm_msg:). When adding a text-only snippet (no attachment), this will raise an UnboundLocalError.
        # Handle optional attachment
        file_id = None
        attachment_info = None
        if ctx.message.attachments:

cogs/modmail.py:363

  • The docstring hard-codes “max 10 MB”, but the actual limit is configurable via snippet_attachment_max_size (default 10). This will become inaccurate if the config is changed.
        You can also attach a file (max 10 MB) to include with the snippet.

Copilot AI review requested due to automatic review settings August 2, 2026 13:07
auto-merge was automatically disabled August 4, 2026 15:42

Pull request was closed

@lorenzo132 lorenzo132 reopened this Aug 4, 2026

@StephenDaDev StephenDaDev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested working, no serious issues found

martinbndr
martinbndr previously approved these changes Aug 6, 2026

@martinbndr martinbndr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

sebkuip
sebkuip previously requested changes Aug 6, 2026

@sebkuip sebkuip left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review for now. Did not check all, and is purely a code review. Have to do some testing later myself and look further into the code.

Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/utility.py Outdated
Comment thread core/config.py Outdated

@StephenDaDev StephenDaDev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please see my mostly formatting consistency comments

Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread core/config_help.json Outdated
Comment thread core/config_help.json Outdated
Copilot AI review requested due to automatic review settings August 7, 2026 12:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (5)

cogs/modmail.py:846

  • The attachment upload error is logged without a traceback, which makes diagnosing intermittent Discord/Mongo/GridFS failures difficult. Log with exc_info=True and avoid stringifying the exception separately.
            except Exception as e:
                logger.error("Failed to upload snippet attachment: %s", e)

core/clients.py:860

  • download_snippet_attachment will currently raise InvalidId or NoFile if the stored file_id is malformed or missing. Callers handle this as a generic exception, but it would be better to translate these expected failure modes into a clear, consistent error (mirroring delete_snippet_attachment, which treats them as benign).
        grid_out = await self.fs.open_download_stream(ObjectId(file_id))

cogs/modmail.py:564

  • The attachment upload error is logged without a traceback, which makes diagnosing intermittent Discord/Mongo/GridFS failures difficult. Log with exc_info=True and avoid stringifying the exception separately.

This issue also appears on line 845 of the same file.

            except Exception as e:
                logger.error("Failed to upload snippet attachment: %s", e)

core/thread.py:2273

  • This warning doesn’t include which snippet attachment failed to convert, which makes it hard to debug if multiple attachments are ever supported or filenames collide. Include the filename (best-effort) in the log message.
                discord_files.append(await att.to_file())
            except Exception:
                logger.warning("Failed to convert snippet attachment to file.", exc_info=True)

core/thread.py:2120

  • Snippet images are embedded via attachment://{filename} before the code attempts to build discord_files. If to_file() fails for any reason, the embed will reference an attachment that wasn’t actually sent, resulting in a broken image. Consider converting to discord.File first and only calling embed.set_image(url="attachment://…") when you know the corresponding file will be included in the send call.
        # Handle snippet images first (embedded directly)
        for a in snippet_images_to_upload:
            if not embedded_image:
                embed.set_image(url=f"attachment://{a.filename}")
                embed.add_field(name="Image", value=a.filename)
                embedded_image = True
            # Always add to files_to_upload so the attachment is physically present
            files_to_upload.append(a)

Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Copilot AI review requested due to automatic review settings August 8, 2026 11:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (3)

cogs/modmail.py:918

  • If you keep an existing attachment when no new file is uploaded, this block should only delete old_file_id when it is being replaced. As written, it will delete the old attachment even for text-only edits.
        if old_file_id:
            old_attachment_deleted = await self._delete_snippet_attachment(old_file_id, name)

cogs/modmail.py:893

  • snippet_edit currently drops old_file_id when no new attachment is provided, so a text-only edit will implicitly remove any existing attachment. That makes it impossible to edit snippet text while keeping the stored attachment, and can cause accidental data loss.

This issue also appears on line 917 of the same file.

        updated_snippet = {"text": new_text or ""}
        if new_file_id:
            updated_snippet["file_id"] = new_file_id

bot.py:1394

  • In alias expansion, command_invocation_text already includes invoked_prefix, but you also prepend invoked_prefix again when creating the StringView. This results in a duplicated prefix (e.g., ??fpreply ...), which can prevent alias->snippet invocations from being parsed correctly.
                    command = self._get_snippet_command()
                    command_invocation_text = f"{invoked_prefix}{command} {snippet_text}"
                view = StringView(invoked_prefix + command_invocation_text)

This solves that if conversion later fails, the embed is sent without its referenced file.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (5)

cogs/modmail.py:865

  • This exception handler logs only the exception message, dropping the traceback. Using logger.exception (or exc_info=True) will preserve stack traces for attachment upload failures during snippet edits.
            except Exception as e:
                logger.error("Failed to upload snippet attachment: %s", e)

core/thread.py:2283

  • When converting snippet attachments to Discord files fails, the warning log doesn’t include which attachment failed, making it hard to diagnose problematic files in production logs. Include the filename (when available) in the warning.
        for att in files_to_upload:
            try:
                discord_files.append(await att.to_file())
            except Exception:
                logger.warning("Failed to convert snippet attachment to file.", exc_info=True)

cogs/modmail.py:577

  • This exception handler logs only the exception message, dropping the traceback. Using logger.exception (or exc_info=True) will preserve stack traces, which is important for diagnosing GridFS upload failures.

This issue also appears on line 864 of the same file.

            except Exception as e:
                logger.error("Failed to upload snippet attachment: %s", e)

bot.py:1322

  • The download failure log drops the traceback and doesn’t include which file_id failed, which makes it hard to debug GridFS/ObjectId issues. Log the file_id and include exc_info=True.
        try:
            file_data, metadata = await self.api.download_snippet_attachment(snippet_data["file_id"])
        except Exception as e:
            logger.warning("Failed to download snippet attachment: %s", e)
            return None

core/clients.py:897

  • On unexpected GridFS deletion failures, the warning log only includes the exception message. Include exc_info=True so the traceback is preserved for debugging.
        except Exception as e:
            logger.warning("Failed to delete snippet attachment %s: %s", file_id, e)
            return False

@StephenDaDev StephenDaDev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested working, no further comments from me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants